package com.ibm.sova.abcd.cma.reqparser;

import java.util.*;
import com.ibm.sova.abcd.cma.dto.*;
import java.lang.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

/**
 * This is an SAX2 handler for parsing CSD data in XML format.
 * 
 * Thread Safety: it is mutable and not thread safe, but it is used in a thread safe way
 */
public class CSDXmlDataHandler extends DefaultHandler {
    
    private final static String NAME_MESSAGE = "Message";
    private final static String NAME_PROD_CONF = "ProductConfiguration";
    private final static String NAME_CONF_ELEM = "ConfigurationElement";
    private final static String NAME_DIS_INFO = "DisplayInformation";
    
    private final static String TYPE_ERROR_MSG = "ErrorMessage";
    private final static String TYPE_PROD_CONFIG = "ProductConfig";
    private final static String TYPE_PROD = "Product";
    private final static String TYPE_FEATURE = "Feature";
    
    private final static String ELEM_MSG = "Message";
    private final static String ELEM_CONF_DESC = "ConfigDescription";
    private final static String ELEM_ELEM_DESC = "ElementDescription";
    
    private final static String ATTR_SEVERITY = "sev";
    private final static String ATTR_PART_NUM = "worldWidePartNumber";
    private final static String ATTR_TYPE_OF_PART = "typeOfPart";
    private final static String ATTR_QUANTITY = "quantity";
    
    private final static String SEVERITY_C = "C";

    /**
     * Represents current XML element being parsed.
     * 
     * It is initialized to null in ctor, can be changed by startElement and endElement method. Can be null, cannot be empty
     */
    private String currentElement;

    /**
     * Represents the entity elements that is processed while parsing the xml document. The first element is the toppest entity processed, and the last element is the most inner entity is currently processed.
     * 
     * Its elements can be "ProductConfig",  "ErrorMessage", "Product" or "Feature".
     * 
     * It is initialized in ctor to null, can be set by startElement and used in endElement methods.
     */
    private List processedEntityTypes;

    /**
     * Represents the result ABCDResponseData entity.
     * 
     * It is initialized in ctor to null, can be set by startDocument method, it is used in endElement method. It has a getter.
     */
    private ABCDResponseData responseData;

    /**
     * Represents current ProductConfiguration entity that is processed.
     * 
     * It is initialized in ctor to null, can be any value, it is used by startElement and endElement methods.
     */
    private ProductConfiguration currentProductConfig;

    /**
     * Represents current Product entity that is processed.
     * 
     * It is initialized in ctor to null, can be any value, it is used by startElement and endElement methods.
     */
    private Product currentProduct;

    /**
     * Represents current Feature entity that is processed.
     * 
     * It is initialized in ctor to null, can be any value, it is used by startElement and endElement methods.
     */
    private Feature currentFeature;

    /**
     * Represents current ErrorMessage entity that is processed.
     * 
     * It is initialized in ctor to null, can be any value, it is used by startElement and endElement methods.
     */
    private ErrorMessage currentErrorMsg;

    /**
     * Creates an instance of this class.
     * 
     * ###Params
     * None
     * 
     * ###Impl
     * None
     * 
     * ###Exceptions
     * None
     */
    public CSDXmlDataHandler() {
    }

    /**
     * Handles the processing of start of XML element.
     * 
     * ###Params
     * uri - the uri
     * localName - the local name
     * qName - the QName
     * attributes - attribute list
     * 
     * ###Impl
     * 1. currentElement=localName;
     * 2. If localName=="Message": 
     *     2.1 Create a new entity: currentErrorMsg=new ErrorMessage();
     *     2.2 Add entity type to processed entity types list: processsedEntityTypes.add("ErrorMessage"); 
     *     2.3 For index=0; index<attributes.getLength(); index++:
     *           2.3.1 If attributes.getLocalName(index) is "sev": 
     *                    2.3.1.1 Set severity: currentErrorMsg.severity=attributes.getValue(index); 
     *                    2.3.1.2 If  currentErrorMsg.severity=="C": responseData.criticalErrors=true;
     * 3. Else if localName=="ProductConfiguration":
     *     3.1 Create a new entity: currentProductConfig=new ProductConfiguration();
     *     3.2 Add entity type to processed entity types list: processedEntityTypes.add("ProductConfig");
     * 4. Else if localName=="ConfigurationElement":
     *     4.1 If last element in processedEntityTypes is "ProductConfig":
     *           4.1.1 Create a new Product entity: currentProduct=new Product();
     *           4.4.2 Add entity type to processed entity types list: processedEntityTypes.add("Product");
     *           4.4.3 For index=0; index<attributes.getLength(); index++:
     *                    4.4.3.1 If attributes.getLocalName(index) is "worldWidePartNumber", then currentProduct.id=attributes.getValue(index); 
     *                    4.4.3.2 Else if attributes.getLocalName(index) is "typeOfPart", then currentProduct.type=attributes.getValue(index);
     *     4.2 Else if last element in processedEntityTypes is "Product":
     *           4.2.1 Create a new Feature entity: currentFeature=new Feature();
     *           4.2.2 Add entity type to processed entity types list: processedEntityTypes.add("Feature");
     *           4.2.3 For index=0; index<attributes.getLength(); index++:
     *                    4.2.3.1 If attributes.getLocalName(index) is "worldWidePartNumber", then currentFeature.id=attributes.getValue(index); break;
     * 5. Else if localName=="DisplayInformation":
     *     5.1 If last element in processedEntityTypes is "Product":
     *           5.1.1 For index=0; index<attributes.getLength(); index++:
     *                    5.1.1.1 If attributes.getLocalName(index) is "quantity", then currentProduct.quantity=<convert attributes.getValue(index) to integer>; break;
     *     5.2 Else if last element in processedEntityTypes is "Feature":
     *           5.2.1 For index=0; index<attributes.getLength(); index++:
     *                    5.2.1.1 If attributes.getLocalName(index) is "quantity", then currentFeature.quantity=<convert attributes.getValue(index) to integer>; break;
     * 
     * ###Exceptions
     * SAXException - If any error occurred
     * @param localName 
     * @param qName 
     * @param attributes 
     * @param uri 
     */
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
        currentElement = localName;
        if(localName.equals(NAME_MESSAGE)) {
            ErrorMessage currentErrorMsg = new ErrorMessage();
            addEntityTypeToProcessedEntityTypes(TYPE_ERROR_MSG);
            for(int index = 0; index < attributes.getLength(); index ++) {
                if(attributes.getLocalName(index).equals(ATTR_SEVERITY)) {
                    currentErrorMsg.setSeverity(attributes.getValue(index));
                    if(currentErrorMsg.getSeverity().equals(SEVERITY_C)) {
                        responseData.setCriticalErrors(true);
                    }
                }
            }
        } else if(localName.equals(NAME_PROD_CONF)) {
            currentProductConfig = new ProductConfiguration();
            addEntityTypeToProcessedEntityTypes(TYPE_PROD_CONFIG);
        } else if(localName.equals(NAME_CONF_ELEM)) {
            String lastProcessedEntityType =  getLastElementInProcessedEntityTypes();
            if(lastProcessedEntityType != null) {
                if(lastProcessedEntityType.equals(TYPE_PROD_CONFIG)) {
                    currentProduct = new Product();
                    addEntityTypeToProcessedEntityTypes(TYPE_PROD);
                    for(int index = 0; index < attributes.getLength(); index ++) {
                        if(attributes.getLocalName(index).equals(ATTR_PART_NUM)) {
                            currentProduct.setId(attributes.getValue(index));
                        } else if(attributes.getLocalName(index).equals(ATTR_TYPE_OF_PART)) {
                            currentProduct.setType(attributes.getValue(index));
                        }
                    }
                } else if(lastProcessedEntityType.equals(TYPE_PROD)) {
                    currentFeature = new Feature();
                    processedEntityTypes.add(TYPE_FEATURE);
                    for(int index = 0; index < attributes.getLength(); index ++) {
                        if(attributes.getLocalName(index).equals(ATTR_PART_NUM)) {
                            currentProduct.setId(attributes.getValue(index));
                            break;
                        }
                    }
                }
            }
        } else if(localName.equals(NAME_DIS_INFO)) {
            String lastProcessedEntityType =  getLastElementInProcessedEntityTypes();
            if(lastProcessedEntityType != null) {
                if(lastProcessedEntityType.equals(TYPE_PROD)) {
                    for(int index = 0; index < attributes.getLength(); index ++) {
                        if(attributes.getLocalName(index).equals(ATTR_QUANTITY)) {
                            currentProduct.setQuantity(Integer.valueOf(attributes.getValue(index)).intValue());
                            break;
                        }
                    }
                } else if(lastProcessedEntityType.equals(TYPE_FEATURE)) {
                    for(int index = 0; index < attributes.getLength(); index ++) {
                        if(attributes.getLocalName(index).equals(ATTR_QUANTITY)) {
                            currentFeature.setQuantity(Integer.valueOf(attributes.getValue(index)).intValue());
                            break;
                        }
                    }
                }
            }
        }
        
    }

    /**
     * Handles end of XML element.
     * 
     * ###Params
     * uri - the uri
     * localName - the local name
     * qName - the QName
     * 
     * ###Impl
     * 1. If localName=="Message":
     *     1.1 Add message to messages list: responseData.overallMessages.add(currentErrorMsg); //note if list is null, create it first
     *     1.2 Set current message to null: currentErrorMsg=null; 
     *     1.3 Remove the last element in processedEntityTypes list;
     * 2. Else if localName=="ProductConfiguration":
     *     2.1 Add product config to response data: responseData.productConfig.add(currentProductConfig); //note if list is null, create it first
     *     2.1 Set current product config to null: currentProductConfig=null;
     *     2.3 Remove the last element in processedEntityTypes list;
     * 3. Else if localName="ConfigurationElement":
     *     3.1 If the last element in processedEntityTypes list is "Product":
     *           3.1.1 Add product to product config's products list: currentProductConfig.products.add(currentProduct); //note if list is null, create it first
     *           3.1.2 Set current product to null: currentProduct=null;
     *           3.1.3 Remove the last element in processedEntityTypes list
     *     3.2 Else if the last element in processedEntityTypes list is "Feature":
     *           3.2.1 Add feature to product's features list: currentProduct.features.add(currentFeature); //note if list is null, create it first
     *           3.2.2 Set current feature to null: currentFeature=null;
     *           3.2.3 Remove the last element in processedEntityTypes list
     * 
     * ###Exceptions
     * SAXException - If any error occurred
     * @param localName 
     * @param qName 
     * @param uri 
     */
    public void endElement(String uri, String localName, String qName) {
        if(localName.equals(NAME_MESSAGE)) {
            if(responseData.getOverallMessages() == null) {
                responseData.setOverallMessages(new ArrayList());
            }
            this.responseData.getOverallMessages().add(currentErrorMsg);
            this.currentErrorMsg = null;
            removeLastElementInProcessedEntityTypes();
        } else if(localName.equals(NAME_PROD_CONF)) {
            if(responseData.getProductConfig() == null) {
                responseData.setProductConfig(new ArrayList());
            }
            this.responseData.getProductConfig().add(currentProductConfig);
            this.currentProductConfig = null;
            removeLastElementInProcessedEntityTypes();
        } else if(localName.equals("NAME_CONF_ELEM")) {
            String lastProcessedEntityType =  getLastElementInProcessedEntityTypes();
            if (lastProcessedEntityType != null) {
                if(lastProcessedEntityType.equals(TYPE_PROD)) {
                    if(currentProductConfig.getProducts() == null) {
                        currentProductConfig.setProducts(new ArrayList());   
                    }
                    currentProductConfig.getProducts().add(currentProduct);
                    currentProduct = null;
                    removeLastElementInProcessedEntityTypes();
                } else if(lastProcessedEntityType.equals(TYPE_FEATURE)) {
                    if(currentProduct.getFeatures() == null) {
                        currentProduct.setFeatures(new ArrayList());
                    }
                    currentProduct.getFeatures().add(currentFeature);
                    currentFeature = null;
                    removeLastElementInProcessedEntityTypes();
                }
            }
        }
        
    }

    /**
     * Handles XML element value.
     * 
     * ###Params
     * ch - the character list
     * start - start of element value in character list
     * length - length of element value
     * 
     * ###Impl
     * 1. If currentElement=="Message": currentErrorMsg.message=new String(ch, start, length);
     * 2. Else if currentElement=="ConfigDescription": currentProductConfig.configDescription=new String(ch, start, length);
     * 3. Else if currentElement="ElementDescription":
     *     3.1 If last element in processedEntityTypes is "Product": currentProduct.description=new String(ch, start, length);
     *     3.2 Else if last element in processedEntityTypes is "Feature": currentFeature.description=new String(ch, start, length);
     * 
     * ###Exceptions
     * SAXException - If any error occurred
     * @param start 
     * @param length 
     * @param ch 
     */
    public void characters(char[] ch, int start, int length) {
        if(currentElement.equals(NAME_MESSAGE)) {
            currentErrorMsg.setMessage(new String(ch, start, length));
        } else if(currentElement.equals(ELEM_CONF_DESC)) {
            currentProductConfig.setConfigDescription(new String(ch, start, length));
        } else if(currentElement.equals(ELEM_ELEM_DESC)) {
            String lastProcessedEntityType =  getLastElementInProcessedEntityTypes();
            if (lastProcessedEntityType != null) {
                if (lastProcessedEntityType.equals(TYPE_PROD)) {
                    currentProduct.setDescription(new String(ch, start, length));
                } else if(lastProcessedEntityType.equals(TYPE_FEATURE)) {
                    currentFeature.setDescription(new String(ch, start, length));
                }
            }
        }
    }

    /**
     * Start parsing XML document.
     * 
     * ###Params
     * None
     * 
     * ###Impl
     * 1. currentElement = null;
     * 2. responseData=new ABCDResponseData();
     * 3. currentEntityTypes = new ArrayList();
     * 4. currentErrorMsg=null;
     * 5. currentProduct=null;
     * 6. currentFeature=null;
     * 7. currentProductConfig=null;
     * 
     * ###Exceptions
     * None
     */
    public void startDocument() {
        this.currentElement = null;
        this.responseData = new ABCDResponseData();
        this.processedEntityTypes = new ArrayList();
        this.currentErrorMsg = null;
        this.currentProduct = null;
        this.currentFeature = null;
        this.currentProductConfig = null;
    }

    /**
     * Gets parsed ABCD response data.
     * 
     * ###Params
     * return - parsed ABCD response data
     * 
     * ###Impl
     * return this.responseData;
     * 
     * ###Exceptions
     * None
     * @return 
     */
    public ABCDResponseData getResponseData() {
        return this.responseData;
    }
    
    
    /**
     * <p>
     * Get Last Element in Processed Entity Types List.
     * </p>
     * @return last Processed Entity Type
     */
    private String getLastElementInProcessedEntityTypes() {
        String lastProcessedEntityType = null;
        if(processedEntityTypes != null && !processedEntityTypes.isEmpty()) {
            lastProcessedEntityType = (String) processedEntityTypes.get(processedEntityTypes.size() - 1);
        }
        return lastProcessedEntityType;
    }
    
    /**
     * <p>
     * Remove Last Element in Processed Entity Types List.
     * </p>
     */
    private void removeLastElementInProcessedEntityTypes() {
        if(processedEntityTypes != null && !processedEntityTypes.isEmpty()) {
            processedEntityTypes.remove(processedEntityTypes.size() - 1);
        }
    }
    

    /**
     * <p>
     * Add an Entity Type to Processed Entity Types List.
     * </p>
     * @param element
     */
    private void addEntityTypeToProcessedEntityTypes(String element) {
        processedEntityTypes.add(element);
    }
}

